<?php
    class ParseError extends Exception { }

    class mbXmlParser {
        private $xml_parser;
        private $factory;
        
        function mbXmlParser() {
            $this->xml_parser = new xmlParser();
            $this->factory    = new mbDefaultFactory();
        }
        
        private function parseList( XMLNode $node, array &$list, $func ) {
            for ( $i = 0; $i < $node->nChildNodes(); $i++ ) {
              $cnode = $node->getChildNode($i);
              $list[] = call_user_func( array( &$this, $func ), $cnode );
            }
        }
        
        private function parseResults( XMLNode $node, array &$list, $func, $type ) {
            for ( $i = 0; $i < $node->nChildNodes(); $i++ ) {
                $cnode = $node->getChildNode($i);
                $object = call_user_func( array( &$this, $func ), $cnode );
                $score = $cnode->getAttribute("ext:score");
                eval( '$to_add = new ' . $type . 'Result($object,$score);' );
                $to_add->setCount( $node->getAttribute("count") );
                $to_add->setOffset( $node->getAttribute("offset") );
                $list[] = $to_add;
            }
        }
        
        private function parseRelations( XMLNode $node, MusicBrainzEntity $entity ) {
            $targetType = $node->getAttribute("target-type");
            if ( $targetType == '' )
              return;

            for ( $i = 0; $i < $node->nChildNodes(); $i++ ) {
                $cnode = $node->getChildNode($i);
                if ( strtolower($cnode->getName()) == "relation" ) {
                    $relation = $this->createRelation( $cnode, $targetType );
                    if ( $relation )
                      $entity->addRelation( $relation );
                }
            }
        }
        
        private function parseUserResults( XMLNode $node, array &$userList ) {
            for ( $i = 0; $i < $node->nChildNodes(); $i++ ) {
                $cnode = $node->getChildNode($i);
                $userList[] = $this->createUser( $cnode );
            }
        }
        
        private function createUser( XMLNode $node ) {
            $user = $this->factory->newUser();
            for ( $i = 0; $i < $node->nChildNodes(); $i++ ) {
                $cNode = $node->getChildNode($i);
                $name = $cNode->getName();
                switch ( $name ) {
                    case "name":
                     $user->setName($cNode->getText());
                     break;
                    case "ext:nag":
                     $user->setShowNag($node->getAttribute("show") == 'true' ? true : false);
                     break;
                }
            }
            return $user;
        }
        
        private function createArtistAlias( XMLNode $node ) {
            $aa = $this->factory->newArtistAlias();
            $aa->setType($node->getAttribute("type"));
            $aa->setScript($node->getAttribute("script"));
            $aa->setValue($node->getText());
            return $aa;

        }
        
        private function createTag( XMLNode $node ) {
            $tag = $this->factory->newTag();
            $tag->setCount($node->getAttribute("count"));
            $tag->setName($node->getText());
            return $tag;
        }
        
        private function createLabelAlias( XML $node ) {
            $la = $this->factory->newLabelAlias();
            $la->setType($node->getAttribute("type"));
            $la->setScript($node->getAttribute("script"));
            $la->setValue($node->getText());
            return $la;
        }
        
        private function createDisc( XMLNode $node ) {
            $disc = $this->factory->newDisc();
            $disc->setId($node->getAttribute("id"));
            return $disc;
        }
        
        private function createReleaseEvent( XMLNode $node ) {
            $relEvent = $this->factory->newReleaseEvent();
            $relEvent->setCountry($node->getAttribute("country"));
            $relEvent->setDate($node->getAttribute("date"));
            $relEvent->setCatalogNumber($node->getAttribute("catalog-number"));
            $relEvent->setBarcode($node->getAttribute("barcode"));
            for ( $i = 0; $i < $node->nChildNodes(); $i++ ) {
                $cnode = $node->getChildNode($i);
                switch ( strtolower($cnode->getName()) ) {
                    case "label":
                     $relEvent->setLabel($this->createLabel($cnode));
                     break;
                }
            }
            return $relEvent;
        }
        
        private function createLabel( XMLNode $node ) {
            $label = $this->factory->newLabel();
            $label->setId($node->getAttribute("id"));
            $label->setType($node->getAttribute("type"));
            for ( $i = 0; $i < $node->nChildNodes(); $i++ ) {
                $cnode = $node->getChildNode($i);
                switch ( strtolower($cnode->getName()) ) {
                    case "name":
                    case "sort-name":
                     $label->setSortName($cnode->getText());
                     break;
                    case "disambiguation":
                     $label->setDisambiguation($cnode->getText());
                     break;
                    case "life-span":
                     $begin = $cnode->getAttribute("begin");
                     $end   = $cnode->getAttribute("end");
                     if ( $begin != "" )
                       $label->setBeginDate($begin);
                     if ( $end != "" )
                       $label->setEndDate($end);
                     break;
                    case "alias-list":
                     $pAlias = &$label->getAliases();
                     $this->parseList( $cnode, $pAlias, 'createAlias' );
                     break;
                    case "release-list":
                     $pRel = &$label->getReleases();
                     $label->setReleasesCount($cnode->getAttribute("count"));
                     $label->setReleasesOffset($cnode->getAttribute("offset"));
                     $this->parseList( $cnode, $pRel, 'createRelease' );
                     break;
                    case "relation-list":
                     $this->parseRelations( $cnode, $label );
                     break;
                    case "tag-list":
                     $pTag = &$label->getTags();
                     $this->parseList( $cnode, $pTag, 'createTag' );
                     break;
                }
            }

            return $label;
        }
        
        private function createArtist( XMLNode $node ) {
            $artist = $this->factory->newArtist();
            $artist->setId($node->getAttribute("id"));
            $artist->setType($node->getAttribute("type"));
            for ( $i = 0; $i < $node->nChildNodes(); $i++ ) {
                $cnode = $node->getChildNode($i);
                switch ( strtolower($cnode->getName()) ) {
                    case "name":
                     $artist->setName($cnode->getText());
                     break;
                    case "sort-name":
                     $artist->setSortName($cnode->getText());
                     break;
                    case "disambiguation":
                     $artist->setDisambiguation($cnode->getText());
                     break;
                    case "life-span":
                     $begin = $cnode->getAttribute("begin");
                     $end   = $cnode->getAttribute("end");
                     if ( $begin != "" )
                       $artist->setBeginDate( $begin );
                     if ( $end != "" )
                       $artist->setEndDate( $end );
                     break;
                    case "alias-list":
                     $pAli = &$artist->getAliases();
                     $this->parseList( $cnode, $pAli, 'createArtistAlias' );
                     break;
                    case "release-list":
                     $pRel = &$artist->getReleases();
                     $artist->setReleasesCount($cnode->getAttribute("count"));
                     $artist->setReleasesOffset($cnode->getAttribute("offset"));
                     $this->parseList( $cnode, $pRel, 'createRelease' );
                     break;
                    case "relation-list":
                     $this->parseRelations( $cnode, $artist );
                     break;
                    case "tag-list":
                     $pTag = &$artist->getTags();
                     $this->parseList( $cnode, $pTag, 'createTag' );
                     break;
                }
            }

            return $artist;
        }
        
        private function createTrack( XMLNode $node ) {
            $track = $this->factory->newTrack();
            $track->setId($node->getAttribute("id"));
            for ( $i = 0; $i < $node->nChildNodes(); $i++ ) {
                $cnode = $node->getChildNode($i);
                switch( strtolower($cnode->getName()) ) {
                    case "title":
                     $track->setTitle($cnode->getText());
                     break;
                    case "artist":
                     $track->setArtist($this->createArtist($cnode));
                     break;
                    case "duration":
                     $track->setDuration($cnode->getText());
                     break;
                    case "release-list":
                     $pRel = &$track->getReleases();
                     $track->setReleasesOffset($cnode->getAttribute("offset"));
                     $track->setReleasesCount($cnode->getAttribute("count"));
                     $this->parseList( $cnode, $pRel, 'createRelease' );
                     break;
                    case "relation-list":
                     $this->parseRelations( $cnode, $track );
                     break;
                    case "tag-list":
                     $pTag = &$track->getTags();
                     $this->parseList( $cnode, $pTag, 'createTag' );
                     break;
                }
            }
            
            return $track;
        }
        
        private function createRelease( XMLNode $node ) {
            $release = $this->factory->newRelease();
            $release->setId($node->getAttribute("id"));
            $array = array();
            $types = $node->getAttribute( "type" );
            $types = explode( ' ', $types );
            foreach ( $types as $one_type )
              $array[] = $one_type;
            $release->setTypes( $array );

            for ( $i = 0; $i < $node->nChildNodes(); $i++ ) {
                $cnode = $node->getChildNode($i);
                switch ( strtolower($cnode->getName()) ) {
                    case "title":
                     $release->setTitle($cnode->getText());
                     break;
                    case "text-representation":
                     $release->setTextLanguage($cnode->getAttribute("language"));
                     $release->setTextScript($cnode->getAttribute("script"));
                     break;
                    case "asin":
                     $release->setAsin($cnode->getText());
                     break;
                    case "artist":
                     $release->setArtist($this->createArtist($cnode));
                     break;
                    case "release-event-list":
                     $pRelEv = &$release->getReleaseEvents();
                     $this->parseList( $cnode, $pRelEv, 'createReleaseEvent' );
                     break;
                    case "disc-list":
                     $pDisc = &$release->getDiscs();
                     $this->parseList( $cnode, $pDisc, 'createDisc' );
                     break;
                    case "track-list":
                     $pTrack = &$release->getTracks();
                     $release->setTracksOffset($cnode->getAttribute("offset"));
                     $release->setTracksCount($cnode->getAttribute("count"));
                     $this->parseList( $cnode, $pTrack, 'createTrack' );
                     break;
                    case "relation-list":
                     $this->parseRelations( $cnode, $release );
                     break;
                    case "tag-list":
                     $pTag = &$release->getTags();
                     $this->parseList( $cnode, $pTag, 'createTag' );
                     break;
                }
            }

            return $release;
        }
        
        private function createRelation( XMLNode $node, $type ) {
            $relation = $this->factory->newRelation();
            $relation->setType(extractFragment($node->getAttribute("type"))); // TODO: fixme
            $relation->setTargetType($type);
            $relation->setTargetId($node->getAttribute("target"));
            
            $dir = Relation::DIR_BOTH;
            switch( strtolower($node->getAttribute("direction")) ) {
                case "forward":
                 $dir = Relation::DIR_FORWARD;
                 break;
                case "backward":
                 $dir = Relation::DIR_BACKWARD;
                 break;
            }
            $relation->setDirection($dir);

            $relation->setBeginDate($node->getAttribute("begin"));
            $relation->setEndDate($node->getAttribute("end"));

            // TODO: ns
            $attrs = $node->getAttribute("attributes");
            $attributes = explode( ' ', $attrs );
            foreach ( $attributes as $attr )
              $relation->addAttribute( $attr );

            $target = null;
            if ( $node->nChildNodes() > 0  ) {
                $cnode = $node->getChildNode(0);
                switch ( strtolower($cnode->getName()) ) {
                  case "artist":
                   $target = $this->createArtist($cnode);
                   break;
                  case "release":
                   $target = $this->createRelease($cnode);
                   break;
                  case "track":
                   $target = $this->createTrack($cnode);
                   break;
                }
            }
            $relation->setTarget($target);

            return $relation;
        }
        
        function parse( $data ) {
            $nodes = $this->xml_parser->parse( $data );

            if ( $nodes == false )
              throw new ParseError( $this->xml_parser->lastError(), 1 );

            $md = new Metadata();

            
            for ( $i = 0; $i < $nodes->nChildNodes(); $i++ ) {
                $node = $nodes->getChildNode($i);
                $name = strtolower($node->getName());

                switch ( $name ) {
                    case "artist":
                     $md->setArtist($this->createArtist($node));
                     break;
                    case "track":
                     $md->setTrack($this->createTrack($node));
                     break;
                    case "release":
                     $md->setRelease($this->createRelease($node));
                     break;
                    case "label":
                     $md->setLabel($this->createLabel($node));
                     break;
                    case "artist-list":
                     $pArt = &$md->getArtistResults();
                     $this->parseResults($node, $pArt, 'createArtist', 'Artist' );
                     break;
                    case "track-list":
                     $pTrack = &$md->getTrackResults();
                     $this->parseResults($node, $pTrack, 'createTrack', 'Track' );
                     break;
                    case "release-list":
                     $pRel = &$md->getReleaseResults();
                     $this->parseResults($node, $pRel, 'createRelease', 'Release' );
                     break;
                    case "ext:user-list":
                     $list = &$md->getUserList();
                     $this->parseUserResults( $node, $list );
                     break;
                }
            }
            return $md;
        }
    }
?>
